美术 のshader 学习笔记,集美术与程序于一身
最近在社区看到美术同学 fkkssj 的 shader 学习笔记,集美术与程序于一身,强得不可思议,搬来分享给大家,建议战略性 mark~
之前是一枚每天快快乐乐的原画,偶然一天在项目中接触到了 shader 这个东西,从此便走上了一条不归路。
其实做的项目基本也不怎么需要 shader 这种东西,但是感觉还挺好玩的,现在比较喜欢在 blender 里面连一些效果,然后看能不能移植到 Cocos 中实现。
想记录一下自己做的一些阴间的东西,万一以后会用上也方便查找找寻思路。
其实从一个什么代码都不懂的美术开始学 shader 这种东西,刚开始的阶段是很痛苦的,自学的过程中也踩了很多坑,但是只要把基本的东西学清楚了,后面就是一个好玩的创造的过程了,也欢迎美术同学和我一起学。
翻出了自己 N 年前画的一个披风图标,给它+点阴间特效。
加完shader:
这个 shader 非常简单,算法也很基础,尽量写的详细,方便一些新手能看懂。
第一步,在 PS 给图标绘制特效遮罩图 红色的部分准备做流光,然后绿色的部分衣摆就是一点飘火的效果,背景的蓝色准备做火焰,这里使用通道也可以使用图层用线性减淡模式也可以。
PS 线性减淡的原理是将图层之间的 RGB 值简单粗暴的相加,和 Cocos 中的粒子默认的混合模式是一样的效果。
然后把背景去掉 最后得到两张这样的图。
此外还用了两张特效贴图。
是这种比较轻的纹理,适合做一些 uv 的偏移。
对比度比较高的这种波纹图,适合做火焰或者流光一类的东西。通道图和流光图加起来不到 100Kb,可以说是非常省资源了,关键是还可以复用啊!
第二步制作流光的效果 新建一个 shader,制作流光的效果,需要一个沿着我画出来的金属遮罩走。
这里因为 uv 的x分量,是从左往右从 0-1,所以使用一个这样的函数,让他产生 0-1-0 的效果 。
再调用我们遮罩图的 r 通道,相乘就得到了一个流光的效果:
void main () {
vec4 o = vec4(1, 1, 1, 1);
vec4 NoiseCol = texture(noiseTex, v_uv0);
float light = NoiseCol.r * pow(abs((fract(v_uv0.x + cc_time.x + 0.5) - 0.5) * 2.0), 2.0);
vec4 test = vec4(light,light,light,1.0);
gl_FragColor = test;
结果:
然后流光的大小范围(用 pow 函数控制)和颜色通过 property 在外部进行调整。
第二步是制作衣摆那里的火焰流光。
然后这里详细解释一下 uv 采样的原理。
然后知道了这个原理,我们来做火焰流光的效果。
首先有这样一张流光贴图:
我们现在要让他产生躁动感。使用给的 uv 偏移的纹理,乘以一个比较小的值,再与引擎本身的 uv 相加。
vec2 NewUV = v_uv0 + UvDisCol.xy * 0.3;
得到了这样一个新的 uv,使用这个 uv 对原来的流光图采样。
vec2 NewUV = v_uv0 + UvDisCol.xy * 0.3;
vec4 WaveCol = texture(_WaveTex, NewUV);
会采样出一些具有扭曲感的效果:
然后如果我们让那个 uv 偏移的纹理随着时间动起来采样的结果是不是就有一种动态的扭曲感觉:
现在在 property 里面定义几个参数方便调控。(这里我习惯于用一个 vec4 来调控图片的 uv,xy 分量调控贴图的缩放,zw 分量调控 uv 的偏移)
properties:
alphaThreshold: { value: 0.5 }
_AlphaTex: { value: white } # 遮罩图
_UvDisTex: { value: white } # uv偏移图
_UvDisTile: { value: [1.0, 1.0, 1.0, 1.0]} # 控制uv偏移图的缩放和偏移
_WaveTex: { value: white } # 火焰流光图
_WaveTile: { value: [1.0, 1.0, 1.0, 1.0]} # 控制火焰流光图的缩放和偏移
_FireCol: { value: [1.0, 1.0, 1.0, 1.0], editor : { type : color }} # 控制火焰颜色
别忘了在片段着色器里面声明一下:
uniform sampler2D _AlphaTex; //uniform规则是图片要单独拎出来声明
uniform sampler2D _UvDisTex;
uniform sampler2D _WaveTex;
uniform suibianqu{ //其他的颜色和浮点要定义在一个结构体中,名字可以随便取
vec4 _UvDisTile; //注意结构体中vec4要在float前面声明,不然会报错
vec4 _WaveTile;
vec4 _FireCol;
};
最后再进行计算:
vec4 AlphaCol = texture(_AlphaTex, v_uv0); //采样获得遮罩贴图
vec4 UvDisCol = texture(_UvDisTex, v_uv0 * _UvDisTile.xy + cc_time.x * _UvDisTile.zw); //采样获得uv偏移贴图,并让它随着时间移动
vec2 NewUV = v_uv0 + UvDisCol.xy * 0.3; //根据采样获得uv偏移贴图,生成一个新的uv用于采样流光贴图
vec4 WaveCol = texture(_WaveTex, NewUV * _WaveTile.xy + cc_time.x * _WaveTile.zw); //根据新的uv采样流光贴图,并让它随着时间移动
vec4 FinalFireCol = AlphaCol.g * WaveCol * _FireCol; //用最后的结果乘以遮罩的g通道,把结果限制在披风的区域
最后把流光和火焰的结果和原图加起来:
gl_FragColor = o + FinalFireCol + Finallight;
就获得了一个基本的流光效果了,颜色也可以在外部进行调整。
然后最后背景的火焰我比较懒,就直接用刚刚衣服上的火焰把 uv 的缩放值和偏移值调整一下,再乘以遮罩的 b 通道,就可以了。
最后衣服上的符文和宝石上的呼吸灯也都是画遮罩做的,这里就不加上去了。完整代码如下:
CCEffect %{
techniques:
- passes:
- vert: vs
frag: fs
blendState:
targets:
- blend: true
rasterizerState:
cullMode: none
properties:
texture: { value: white }
alphaThreshold: { value: 0.5 }
_AlphaTex: { value: white } # 遮罩图
_UvDisTex: { value: white } # uv偏移图
_UvDisTile: { value: [1.0, 1.0, 1.0, 1.0]} # 控制uv偏移图的缩放和偏移
_UvDisBgTile: { value: [1.0, 1.0, 1.0, 1.0]} # 控制uv偏移图的缩放和偏移
_UvDisPow: { value : 0.3 } # 控制uv偏移图的强度
_WaveTex: { value: white } # 火焰流光图
_WaveTile: { value: [1.0, 1.0, 1.0, 1.0]} # 控制火焰流光图的缩放和偏移
_WaveBgTile: { value: [1.0, 1.0, 1.0, 1.0]} # 控制背景流光图的缩放和偏移
_FireCol: { value: [1.0, 1.0, 1.0, 1.0], editor : { type : color }} # 控制火焰颜色
_FireBgCol: { value: [1.0, 1.0, 1.0, 1.0], editor : { type : color }} # 控制背景颜色
_LightCol: { value: [1.0, 1.0, 1.0, 1.0], editor : { type : color }} # 控制流光颜色
}%
CCProgram vs %{
precision highp float;
#include <cc-global>
#include <cc-local>
in vec3 a_position;
in vec4 a_color;
out vec4 v_color;
in vec2 a_uv0;
out vec2 v_uv0;
void main () {
vec4 pos = vec4(a_position, 1);
#if CC_USE_MODEL
pos = cc_matViewProj * cc_matWorld * pos;
#else
pos = cc_matViewProj * pos;
#endif
v_uv0 = a_uv0;
v_color = a_color;
gl_Position = pos;
}
}%
CCProgram fs %{
precision highp float;
#include <alpha-test>
#include <cc-global> //因为后面火焰动画需要时间值,这个头文件里包含了cc_time.x就是shader运行的时间所以要在这里声明出来
#include <texture>
in vec4 v_color;
in vec2 v_uv0;
uniform sampler2D texture;
uniform sampler2D _AlphaTex; //uniform规则是图片要单独拎出来声明
uniform sampler2D _UvDisTex;
uniform sampler2D _WaveTex;
uniform suibianqu{ //其他的颜色和浮点要定义在一个结构体中,名字可以随便取
vec4 _UvDisTile; //注意结构体中vec4要在float前面声明,不然会报错
vec4 _WaveTile;
vec4 _FireCol;
vec4 _FireBgCol;
vec4 _LightCol;
vec4 _UvDisBgTile;
vec4 _WaveBgTile;
float _UvDisPow;
};
void main () {
vec4 o = vec4(1, 1, 1, 1);
o *= v_color;
#if USE_TEXTURE
CCTexture(texture, v_uv0, o);
#endif
vec4 AlphaCol = texture(_AlphaTex, v_uv0); //采样获得遮罩贴图
//衣摆上的火焰
vec4 UvDisCol = texture(_UvDisTex, v_uv0 * _UvDisTile.xy + cc_time.x * _UvDisTile.zw); //采样获得uv偏移贴图,并让它随着时间移动
vec2 NewUV = v_uv0 + UvDisCol.xy * _UvDisPow; //根据采样获得uv偏移贴图,乘以一个强度值,生成一个新的uv用于采样流光贴图
vec4 WaveCol = texture(_WaveTex, NewUV * _WaveTile.xy + cc_time.x * _WaveTile.zw); //根据新的uv采样流光贴图,并让它随着时间移动
vec4 FinalFireCol = AlphaCol.g * WaveCol * _FireCol; //用最后的结果乘以遮罩的g通道,把结果限制在披风的区域
//流光
vec4 Finallight = _LightCol * AlphaCol.r * pow(abs((fract(v_uv0.x - cc_time.x + 0.5) - 0.5) * 2.0), 10.0); //计算金属流光的颜色
//背景上的火焰
vec4 UvDisBgCol = texture(_UvDisTex, v_uv0 * _UvDisBgTile.xy + cc_time.x * _UvDisBgTile.zw); //采样获得uv偏移贴图,并让它随着时间移动
vec2 NewBgUV = v_uv0 + UvDisBgCol.xy * _UvDisPow; //根据采样获得uv偏移贴图,乘以一个强度值,生成一个新的uv用于采样流光贴图
vec4 WaveBgCol = texture(_WaveTex, NewBgUV * _WaveBgTile.xy + cc_time.x * _WaveBgTile.zw); //根据新的uv采样流光贴图,并让它随着时间移动
vec4 FinalFireBgCol = 2.0 * AlphaCol.b * WaveBgCol * _FireBgCol; //用最后的结果乘以遮罩的g通道,把结果限制在披风的区域
gl_FragColor = o + FinalFireCol + Finallight + FinalFireBgCol;
}
}%
再随便加一点阴间的粒子,敷衍一下赶快结束
具体我就不细调了,这个是我之前做的,源文件已经不见了,现在重做一遍也不太好调的跟之前一摸一样 。
贴图和代码上面都给了,想试试可以直接复制粘贴大法。自己调整一些 tile 的参数可以实现一些不同的效果。
另外通道图也可以自己擦除一部分区域,比如衣摆被上面肩甲遮住的投影那里应该暗一些。总体来说是个很基础的东西了,刚学习 shader 的同学可以试一试。
可以看到最后采样了 5 次贴图,虽然贴图大小可以压缩到 256*256,但是能省还是尽量省,自己做着玩就无所谓了,实际项目中贴图的采样次数还是尽量减少,尤其是1024以上分辨率的贴图。
以上就是今天想跟大家分享的内容,再次感谢作者 fkkssj 的倾情分享,点击【阅读原文】前往社区玩耍,与作者快乐交流噢